Repository navigation
π fix(deploy): reflect intercepted 403/404 from the ingress error backend instead of answering 503 - #17432
Conversation
β¦kend instead of answering 503 The controller's global custom-http-errors list on the hub's cluster included 403 and 404, and the hive-error-pages backend had no branch for either, so every unknown URL on hive.hivecommons.dev was served as a 503 "Hive is restarting" page with a meta-refresh. Search Console flagged it as Soft 404. Add notfound.html/notfound.json, route 403/404 to them, reflect the status in the location / block, and document that the global list must stay 502,503,504. Fixes #17431 Signed-off-by: Andrew Anderson <andy@clubanderson.com>
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Open PR overlap checkBase branch: Conflicts are checked with every PR merged onto the current tip of No conflicting open PRs or same-file overlaps were found. |
eb92f68
βΉοΈ Sentinel notice β informational; author is trusted (owner), merge is not blockedHive flagged this PR (author @clubanderson, head
Hive did not add the |
|
Quality note: this is a behaviour change to the error backend, and A test in the same file, using the existing helpers, that fails on // TestErrorBackendReflects403And404 pins #17431: an intercepted 403/404 must be
// answered with that status and a document that tells no one to "retry
// shortly" β no auto-refresh, and noindex so search engines do not record a
// Soft 404 against a healthy hub.
func TestErrorBackendReflects403And404(t *testing.T) {
docs, conf := errorPagesConfigMaps(t)
directives := stripNginxComments(conf["default.conf"])
for _, code := range []string{"403", "404"} {
if !regexp.MustCompile(`return\s+` + code + `\b`).MatchString(directives) {
t.Errorf("default.conf never returns %s, so an intercepted %s falls through to the 503 page (#17431)", code, code)
}
}
if !regexp.MustCompile(`error_page\s+403\s+404\s+/notfound\.`).MatchString(directives) {
t.Error("default.conf does not route 403/404 to the notfound document (#17431)")
}
body, ok := docs["notfound.json"]
if !ok {
t.Fatal("hive-error-pages ConfigMap has no notfound.json for API callers")
}
var parsed map[string]any
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
t.Fatalf("notfound.json is not valid JSON: %v", err)
}
if parsed["ok"] != false {
t.Errorf(`notfound.json "ok" = %v, want false`, parsed["ok"])
}
html, ok := docs["notfound.html"]
if !ok {
t.Fatal("hive-error-pages ConfigMap has no notfound.html")
}
lower := strings.ToLower(html)
if !strings.Contains(lower, `name="robots" content="noindex"`) {
t.Error("notfound.html is indexable; Search Console will record a Soft 404 again (#17431)")
}
if strings.Contains(lower, "http-equiv=\"refresh\"") || strings.Contains(lower, "settimeout") || strings.Contains(lower, "location.reload") {
t.Error("notfound.html auto-refreshes; a missing page is not a 'retry shortly' state")
}
}No push from this lane (your branch) β adding it to this PR keeps the pin and the fix in one commit. π Hive Agent: β hive: agent=quality backend=copilot model=claude-fable-5.1 copilot=1.0.88 |
There was a problem hiding this comment.
Read the full src/deploy/k8s/error-pages.yaml at the v5 base alongside the diff. The nginx wiring is consistent with the existing 401 path and I found no defect:
hive-error-pagesConfigMap is mounted whole at/usr/share/nginx/html(error-pages.yaml:216-218,243-244), so the newnotfound.html/notfound.jsonkeys become servable files without a volumeitems:change.error_page 403 404 /notfound.$hive_error_ext;sits next to the existingerror_page 401 /unauthorized.$hive_error_ext;, and$hive_error_extis already selected byX-Original-URI(/api/β json) thenX-Format(error-pages.yaml:148-155), so API callers get the JSON body and browsers the HTML, same as today for 401.- The two
if ($http_x_code = ...) { return ...; }lines mirror the 401/500/502/504 ones, so a 404 no longer falls through to thereturn 503default. - Body and diff agree: no Go change, changelog fragment present, and the header comment pins the global
custom-http-errorslist to502,503,504.
One semantic note, not a defect: a 403 is now answered with "not found" copy ({"ok":false,"error":"not found",β¦} / "Page not found"). That is a reasonable choice for a public hub, but if 403 ever does get intercepted β e.g. an API authorization denial behind an Ingress whose list includes 403 β a client keying off error will see "not found" rather than "forbidden". If that matters, a separate forbidden.json is a small follow-up.
This touches a deploy manifest in src/deploy/k8s/, which is a trust surface under my review policy, so I am recording this for a maintainer rather than calling it clean myself. Facts for whoever signs off: author is @clubanderson (repository owner); the change adds static error documents and two status reflections to the error backend; no image, resource limits, RBAC, Service, or probe changes.
β hive: agent=reviewer-2 backend=copilot model=claude-fable-5.1 copilot=1.0.88
There was a problem hiding this comment.
Style / conventions review (read at head eb92f68)
The 403/404 branches follow the existing 401 shape exactly (error_page β internal locations β return in location /), the whole hive-error-pages ConfigMap is mounted without items: (src/deploy/k8s/error-pages.yaml:298-300), so the new keys land automatically. One convention gap:
Add a test pin for the 403/404 branches (in-scope)
Every status branch in this backend is pinned by a Go test that reads the manifest: 502/503 in src/pkg/hub/error_pages_backend_test.go:110-114 (#4241), 401 in src/pkg/hub/saas_api_xhr_ingress_test.go:165-193 (#7405). This PR adds error_page 403 404 /notfound.$hive_error_ext (error-pages.yaml:220) and return 403 / return 404 (error-pages.yaml:239-240) with no pin, while #17431 and the PR body call it "the safety net β¦ so an intercepted 404 can never again become a 503". In this repo "never again" is a test; without one a future cleanup of location / would silently regress to return 503 (error-pages.yaml:246). Suggested addition to src/pkg/hub/error_pages_backend_test.go, mirroring the 401 pin:
+// TestErrorBackendReflectsNotFound pins #17431: an intercepted 403 or 404 must
+// come back as that status with the not-found documents, never the 503
+// "Hive is restarting" fallback (which Search Console reported as Soft 404).
+func TestErrorBackendReflectsNotFound(t *testing.T) {
+ docs, conf := errorPagesConfigMaps(t)
+ directives := stripNginxComments(conf["default.conf"])
+ for _, code := range []string{"403", "404"} {
+ if !regexp.MustCompile(`return\s+` + code + `\b`).MatchString(directives) {
+ t.Errorf("default.conf never returns %s, so an intercepted %s falls through to the 503 fallback (#17431)", code, code)
+ }
+ }
+ if !regexp.MustCompile(`error_page\s+403\s+404\s+/notfound\.\$hive_error_ext`).MatchString(directives) {
+ t.Error("default.conf does not map 403/404 onto the notfound.{json,html} documents")
+ }
+ for _, name := range []string{"notfound.json", "notfound.html"} {
+ if !strings.Contains(directives, "location = /"+name+" { internal; }") {
+ t.Errorf("default.conf has no internal location for /%s", name)
+ }
+ }
+ body, ok := docs["notfound.json"]
+ if !ok {
+ t.Fatal("hive-error-pages ConfigMap has no notfound.json")
+ }
+ var parsed map[string]any
+ if err := json.Unmarshal([]byte(body), &parsed); err != nil {
+ t.Fatalf("notfound.json is not valid JSON: %v", err)
+ }
+ if parsed["ok"] != false {
+ t.Errorf(`notfound.json must report "ok": false, got %v`, parsed["ok"])
+ }
+ if parsed["error"] != "not found" {
+ t.Errorf(`notfound.json "error" = %v, want "not found"`, parsed["error"])
+ }
+ html := docs["notfound.html"]
+ if strings.Contains(html, `http-equiv="refresh"`) {
+ t.Error("notfound.html auto-refreshes; a 404 is not a retry state (#17431)")
+ }
+ if !strings.Contains(html, `name="robots" content="noindex"`) {
+ t.Error("notfound.html lacks noindex; Search Console would index the error page")
+ }
+}Minor (non-blocking): notfound.html links in #58a6ff (error-pages.yaml:92) where unauthorized.html uses the brand amber #f0b429 (error-pages.yaml:140); and the .bee class now wraps π rather than a bee (error-pages.yaml:99). Cosmetic only.
β hive: agent=ci-maintainer backend=copilot model=claude-fable-5.1 copilot=1.0.88
Review follow-ups (below
|
|
Hive status β updated in place; edits do not notify. Full history is in the Activity Monitor. Review findings from Correctness review (read at head 391d212, which superseded the dispatched eb92f68). The nginx mechanics are sound: 1. The new branches are the only ones in this backend without a pinning test (medium)Every other status this backend reflects is guarded: +// TestErrorBackendReflects404NotAs503 pins #17431: an intercepted 403 or 404
+// must come back as that status with the not-found documents, never as the
+// 503 "restarting" page that Search Console reported as a Soft 404.
+func TestErrorBackendReflects404NotAs503(t *testing.T) {
+ docs, conf := errorPagesConfigMaps(t)
+ directives := stripNginxComments(conf["default.conf"])
+ for _, code := range []string{"403", "404"} {
+ if !regexp.MustCompile(`return\s+` + code + `\b`).MatchString(directives) {
+ t.Errorf("default.conf never returns %s; an intercepted %s falls through to 503 (#17431)", code, code)
+ }
+ }
+ if !regexp.MustCompile(`error_page\s+403\s+404\s+/notfound\.\$hive_error_ext`).MatchString(directives) {
+ t.Error("default.conf does not map 403/404 onto the notfound.{json,html} documents")
+ }
+ for _, name := range []string{"notfound.json", "notfound.html"} {
+ if _, ok := docs[name]; !ok {
+ t.Errorf("hive-error-pages ConfigMap has no %s", name)
+ }
+ }
+ var parsed map[string]any
+ if err := json.Unmarshal([]byte(docs["notfound.json"]), &parsed); err != nil {
+ t.Fatalf("notfound.json is not valid JSON: %v", err)
+ }
+ if parsed["ok"] != false {
+ t.Errorf(`notfound.json must report "ok": false, got %v`, parsed["ok"])
+ }
+ if html := docs["notfound.html"]; strings.Contains(html, `http-equiv="refresh"`) || !strings.Contains(html, `name="robots" content="noindex"`) {
+ t.Error("notfound.html must not auto-refresh and must carry noindex (Soft 404, #17431)")
+ }
+}2. Advisory: a reflected 403 tells API callers
|
|
merged by Hive trusted-author auto-merge: author @clubanderson holds role owner and GitHub permission admin on repo; CI green at 391d212. |
|
Thank you for your contribution! Your PR has been merged. We'd love to hear how your experience was: share feedback |
Fixes #17431
The OKE ingress-nginx controller had a global
custom-http-errors: "403,404,502,503,504"; the hub Ingress inherited it, andhive-error-pageshad no 403/404 branch, so every missing hub URL was answered as a 503 "Hive is restarting" page. Google Search Console reported Soft 404 onhivecommons.dev.Live fix already applied (global list β
502,503,504; hub unknown paths now 404). This PR is the safety net in the backend:notfound.html/notfound.jsondocuments (no spinner, no refresh,noindex)error_page 403 404 /notfound.$hive_error_extlocation /reflects 403 and 404 instead of falling through to 503No Go changes; changelog fragment included.